﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FNN.CustomFunctions;
using FNN.NetworkComponents;

namespace FNN.Networks
{
    [Serializable]
    public class Network
    {
        protected string NetworkNameValue;
        protected readonly Random RndNumber = new Random();
        protected int InputCountValue;
        protected int LayerCountValue;
        protected List<Layer> LayersList;
        protected int NeuronInLayerValue;
        protected int OutputCountValue;
        protected List<Synapse> SynapsesList;

        /// <summary>
        ///     Конструирование сети с заданной активационной функцией
        /// </summary>
        /// <param name="inputCount">Количество входных нейронов</param>
        /// <param name="layerCount">Количество слоев</param>
        /// <param name="layerNeuronsCount">Количество нейронов в слое</param>
        /// <param name="functions">Активационные функции</param>
        protected Network(int inputCount, int layerCount, int layerNeuronsCount, List<FunctionTypes> functions)
        {
            InputCountValue = inputCount;
            LayerCountValue = layerCount;
            NeuronInLayerValue = layerNeuronsCount;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            LayersList.Add(new Layer(1, inputCount, true, functions));

            for (var i = 1; i < layerCount; i++)
            {
                LayersList.Add(new Layer(i + 1, layerNeuronsCount, false, functions));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }
        }

        /// <summary>
        ///     Конструктор с указанием количества выходов
        /// </summary>
        /// <param name="inputCount">Количество входов</param>
        /// <param name="layerCount">Количество слоев</param>
        /// <param name="layerNeuronsCount">Количество нейронов в слое</param>
        /// <param name="functions">Активационные функции</param>
        /// <param name="outputCount">Количество выходов</param>
        protected Network(int inputCount, int layerCount, int layerNeuronsCount, List<FunctionTypes> functions,
            int outputCount)
        {
            InputCountValue = inputCount;
            LayerCountValue = layerCount;
            OutputCountValue = outputCount;
            NeuronInLayerValue = layerNeuronsCount;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            LayersList.Add(new Layer(1, inputCount, true, functions));
            //TODO: Оптимизировать цикл
            for (var i = 1; i < layerCount; i++)
            {
                LayersList.Add(new Layer(i + 1, layerNeuronsCount, false, functions));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }

            var lastLayer = new Layer(layerCount + 1, outputCount, true, functions);
            foreach (var inputNeuron in LayersList.Last().Neurons)
            {
                foreach (var outputNeuron in lastLayer.Neurons)
                {
                    SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                }
            }
            LayersList.Add(lastLayer);
        }

        /// <summary>
        ///     Конструктор для создания сети со случайными параметрами
        /// </summary>
        protected Network()
        {
            var randomizer = new Random();
            InputCountValue = randomizer.Next(50, 200);
            LayerCountValue = randomizer.Next(1, 10);
            NeuronInLayerValue = -1;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();
            LayersList.Add(new Layer(1, InputCountValue, true));

            for (var i = 1; i < LayerCountValue; i++)
            {
                var layerNeuronCount = randomizer.Next(2, 32);
                LayersList.Add(new Layer(i + 1, layerNeuronCount, false));
                foreach (var inputNeuron in LayersList[i - 1].Neurons)
                {
                    foreach (var outputNeuron in LayersList[i].Neurons)
                    {
                        SynapsesList.Add(new Synapse(RndNumber.NextDouble()*(-0.1) + 0.05, inputNeuron, outputNeuron));
                    }
                }
            }
        }

        /// <summary>
        ///     Конструктор копирования
        /// </summary>
        /// <param name="source">Исходный экземпляр</param>
        protected Network(Network source)
        {
            InputCountValue = source.InputCountValue;
            LayerCountValue = source.LayerCountValue;
            NeuronInLayerValue = source.NeuronInLayerValue;

            LayersList = new List<Layer>();
            SynapsesList = new List<Synapse>();

            foreach (var layer in source.LayersList)
            {
                LayersList.Add(new Layer(layer));
            }

            foreach (var synapse in source.SynapsesList)
            {
                SynapsesList.Add(new Synapse(synapse));
            }
        }

        /// <summary>
        ///     Количество входов
        /// </summary>
        public int InputCount
        {
            get { return InputCountValue; }
            set { InputCountValue = value; }
        }

        /// <summary>
        ///     Количество слоев
        /// </summary>
        public int LayerCount
        {
            get { return LayerCountValue; }
            set { LayerCountValue = value; }
        }

        /// <summary>
        ///     Количество нейронов в слое
        /// </summary>
        public int NeuronInLayer
        {
            get { return NeuronInLayerValue; }
            set { NeuronInLayerValue = value; }
        }

        /// <summary>
        ///     Синапсы
        /// </summary>
        public List<Synapse> Synapses
        {
            get { return SynapsesList; }
            set { SynapsesList = value; }
        }

        /// <summary>
        ///     Слои
        /// </summary>
        public List<Layer> Layers
        {
            get { return LayersList; }
            set { LayersList = value; }
        }

        /// <summary>
        /// Количество выходов
        /// </summary>
        public int OutputCount
        {
            get { return OutputCountValue; }
            set { OutputCountValue = value; }
        }

        /// <summary>
        ///     Имя сети
        /// </summary>
        public string NetworkName
        {
            get { return NetworkNameValue; }
            set { NetworkNameValue = value; }
        }

        /// <summary>
        ///     Пересчитать веса в синапсах
        /// </summary>
        /// <param name="indexDictionary">Словарь индексов</param>
        public void RecalculateWeights(ref Dictionary<int, double> indexDictionary)
        {
            for (var i = 0; i < indexDictionary.Keys.Count; i++)
            {
                var key = indexDictionary.Keys.ElementAt(i);
                var synapse = Synapses.ElementAt(key);
                indexDictionary[key] = synapse.Weight;
                synapse.Weight = synapse.Weight + RndNumber.NextDouble()*(-0.04) + 0.02;
            }
        }

        /// <summary>
        ///     Пересчитать вес в синапсе
        /// </summary>
        /// <param name="index">Индекс синапса</param>
        /// <returns>Старое значение веса</returns>
        public double RecalculateWeights(int index)
        {
            var synapse = Synapses.ElementAt(index);
            var oldValue = synapse.Weight;
            synapse.Weight = synapse.Weight + RndNumber.NextDouble()*(-0.02) + 0.01;
            return oldValue;
        }

        /// <summary>
        ///     Отменить изменения в синапсах
        /// </summary>
        /// <param name="indexDictionary"></param>
        public void CancelChange(Dictionary<int, double> indexDictionary)
        {
            foreach (var key in indexDictionary.Keys)
            {
                var synapse = Synapses.ElementAt(key);
                synapse.Weight = indexDictionary[key];
            }
        }

        /// <summary>
        ///     Отменить изменение в синапсе
        /// </summary>
        /// <param name="index">Индекс синапса</param>
        /// <param name="oldValue">Старый вес</param>
        public void CancelChange(int index, double oldValue)
        {
            var synapse = Synapses.ElementAt(index);
            synapse.Weight = oldValue;
        }

        /// <summary>
        ///     Получить результат сети
        /// </summary>
        /// <returns>Результат сети</returns>
        public double GetNetworkResult()
        {
            for (var i = 1; i < LayersList.Count; i++)
            {
                var currentIndex = i;
                Parallel.For(0, LayersList.ElementAt(i).Neurons.Count,
                    (k, state) =>
                    {
                        LayersList.ElementAt(currentIndex).Neurons.ElementAt(k).CalculateInput(SynapsesList);
                    });
            }

            var lastLayer = LayersList.LastOrDefault();

            return (lastLayer != null) ? lastLayer.Neurons.Sum(o => o.GetResult()) : 0;
        }

        /// <summary>
        ///     Получить результаты сети
        /// </summary>
        /// <returns>Результаты сети</returns>
        public List<double> GetNetworkResults()
        {
            var results = new List<double>();

            for (var i = 1; i < LayersList.Count; i++)
            {
                var currentIndex = i;
                Parallel.For(0, LayersList.ElementAt(i).Neurons.Count,
                    (k, state) =>
                    {
                        LayersList.ElementAt(currentIndex).Neurons.ElementAt(k).CalculateInput(SynapsesList);
                    });
            }

            var lastLayer = LayersList.LastOrDefault();

            if (lastLayer != null)
                results.AddRange(lastLayer.Neurons.Select(outputNeuron => outputNeuron.GetResult()));


            return results;
        }
    }
}